package hit.core;
import java.util.Iterator;
import java.util.Observable;

/**
 * Facade to connecting the HIT controller to the models products. Also signals controller
 * 	when updates have occurred.
 */
public class ProductFacade extends Observable{
	
	private ProductIndex productIndex;
	
	/**
	 * Creates a new instance of the ProductFacade.
	 */
	public ProductFacade() {
		productIndex = HomeInventoryTracker.Instance().GetProductIndex();
	}
	
	/**
	 * Creates a new instance of the {@link ProductFacade} with the specified 
	 * {@link ProductIndex}
	 * @param productIndex the ProductIndex to use
	 * */
	public ProductFacade(ProductIndex productIndex) {
		this.productIndex = productIndex;
	}
	
	
	/**
	 * Looks for an existing {@link Product} based on its {@link Barcode}
	 * @param barcode The {@link Barcode} of the product we are looking for.
	 * @return The {@link Product} that was found. Null if nothing was returned.
	 * @throws IllegalArgumentException if Barcode is invalid or is local.
	 */
	public Product GetProduct(String barcode) throws IllegalArgumentException {
		try{
			Barcode newCode = new Barcode(barcode);
			return productIndex.GetByBarcode(newCode);
		}
		catch (IllegalArgumentException e){
			throw e;
		}
	}
	
	/**
	 * The iterator for all the products in the system.
	 * @return The iterator for products.
	 */
	public Iterator<Product> GetProductIterator() {
		return productIndex.GetProductIterator();
	}
	
	/**
	 * Returns the Iterator for a set of {@link Product}s contained 
	 * 	within a particular {@link ProductContainer}
	 * @param container The {@link ProductContainer} to search for. If
	 *  container is null, returns the iterator for all products in the
	 *  system.
	 * @return The iterator for the given set.
	 * @throws IllegalArgumentException if the container is not found.
	 */
	public Iterator<Product> GetProductsByContainer(ProductContainer container) {
		
		if (container == null) {
			return productIndex.GetProductIterator();
		}
		else {
			return productIndex.GetByProductContainer(container);
		}
		
	}
	
	
	/**
	 * Checks to see if the new {@link Product} is valid to be added to the list.
	 * @param product The {@link Product} to check.
	 * @return True if the product can be added, false otherwise.
	 */
	public boolean CanAddProduct(Product product) {
		return productIndex.CanAddProduct(product);
	}
	
	/**
	 * Checks to see if the requested edits for a given {@link Product} are valid.
	 * @param original The reference to the original product.
	 * @param edited The product containing the requested changes
	 * @return True if it can be edited. False otherwise.
	 */
	public boolean CanEditProduct(Product original, Product edited) {
		return productIndex.CanEditProduct(original, edited);
	}

	/**
	 * Checks if a {@link product} can be removed from the system.
	 * @param product The product to be removed
	 * @return True if it can be removed, false otherwise.
	 */
	public boolean CanRemoveProduct(Product product) {
		return productIndex.CanRemoveProduct(product);
	}
	
	/**
	 * Checks if a {@link Product} can be removed from a given {@link ProductContainer} 
	 * @param product The product to be removed
	 * @param container The container the product should be removed from.
	 * @return True if the product can be removed from the container. False otherwise.
	 */
	public boolean CanRemoveProductFromContainer(Product product, ProductContainer container) {
		return productIndex.CanRemoveProductFromContainer(product, container);
	}
	
	
	/**
	 * Adds a new {@link Product} to the system.
	 * @param product The new {@link Product} to be added.
	 * @param container The {@link ProductContainer} the {@link Product} should be placed into.
	 * @throws IllegalArgumentException if Product or Container is invalid
	 */
	public Product AddProduct(Product product) 
			throws IllegalArgumentException {
		
		if (!Product.IsValid(product))
			throw new IllegalArgumentException();
		
		if (!productIndex.CanAddProduct(product))
			throw new IllegalArgumentException();
		
		productIndex.AddProduct(product);
		
		ProductNotification notification =
				new ProductNotification(ProductNotification.Type.PRODUCT_ADDED, product);
		
		this.setChanged();
		super.notifyObservers(notification);
		
		return product;
	}
	
	
	/**
	 * Edits an existing {@link Product} in the system.
	 * @param before The {@link Product} to be edited.
	 * @param after The {@link Product} containing the changes to be made.
	 * @throws IllegalArgumentException if either Product is invalid.
	 */
	public ProductNotification EditProduct(Product before, Product after) 
			throws IllegalArgumentException
	{		
		productIndex.EditProduct(before, after);
		
		ProductNotification notification = 
				new ProductNotification(ProductNotification.Type.PRODUCT_EDITED, before);
		
		this.setChanged();
		super.notifyObservers(notification);
		
		return notification;
	}
	
	
	/**
	 * Removes an existing {@link Product} from the system. 
	 * @param product The product to be removed from the system.
	 * @throws IllegalArgumentException Product is invalid.
	 */
	public ProductNotification RemoveProduct(Product product) throws IllegalArgumentException {
		
		if (!Product.IsValid(product))
			throw new IllegalArgumentException();
		
		productIndex.RemoveProduct(product);
		
		ProductNotification notification =
				new ProductNotification(ProductNotification.Type.PRODUCT_DELETED, product);
		
		this.setChanged();
		super.notifyObservers(notification);
		
		return notification;
	}
	
	/**
	 * Removes a {@link Product} from a {@link ProductContainer} and updates the index
	 * @param product the Product to be removed.
	 * @param container the ProductContainer to add the Product to.
	 * @throws IllegalArgumentException if the Product is not valid or not in ProductContainer
	 * */
	public void RemoveProductFromContainer(Product product, ProductContainer container) {
		productIndex.RemoveProductFromContainer(product, container);
		ProductNotification notification =
			new ProductNotification(ProductNotification.Type.PRODUCT_DELETED, product);
	
		this.setChanged();
		super.notifyObservers(notification);
	}
	
	/**
	 * Transfers a {@link product} to a given {@link ProductContainer}. If the product is not
	 * 	already within that {@link StorageUnit}, then the product is simply added to the
	 *  destination.
	 * @param product The product to be transfered.
	 * @param destination The ProductContainer the product should be transfered to
	 * @throws IllegalArgumentException If either the product or the destination are invalid.
	 */
	public void TransferProduct(Product product, ProductContainer destination) 
															throws IllegalArgumentException{
		
		productIndex.TransferProduct(product, destination);
			
		ProductNotification notification =
				new ProductNotification(ProductNotification.Type.PRODUCT_TRANSFERRED, product);
		
		this.setChanged();
		super.notifyObservers(notification);
	}
}
